package mbus

import (
	"encoding/binary"
	"errors"
	"fmt"
	"math"
	"regexp"
	"strconv"
	"strings"
)

// ValidType 验证类型
func ValidType(typ string) bool {
	if typ == "BOOL" || typ == "I16" || typ == "U16" || typ == "I32" || typ == "U32" || typ == "FLOAT" {
		return true
	}
	return false
}

// GetFType 转换完整类型
func GetFType(tp string) string {
	switch tp {
	case "W":
		return "U16"
	case "I":
		return "I16"
	case "D":
		return "U32"
	case "X":
		return "BOOL"
	case "R":
		return "FLOAT"
	default:
		return "FLOAT"
	}
}

// ParseTag  H(03 Holding AO保持寄存器)  I(04 Input AI输入寄存器)  C(01 Coil DO线圈状态寄存器)  D(02 DInput DO离散输入寄存器)
func ParseTag(code, typ string) (tag *Tag, err error) {
	code = strings.ToUpper(code) //upper
	typ = strings.ToUpper(typ)
	code = strings.Replace(code, " ", "", -1) //remove spaces
	if code == "" {
		err = fmt.Errorf("input code is invalid, code should be S7 syntax")
		return
	}
	area := "H"
	addr := code
	re1 := regexp.MustCompile(`^[HCDI](\d+)(?:\.([0-9]|1[0-5]))?$`)
	if re1.MatchString(addr) {
		area = addr[0:1]
		addr = addr[1:]
	}
	if area == "" || !(area == "H" || area == "C" || area == "D" || area == "I") {
		err = fmt.Errorf("input area is invalid")
		return
	}
	if typ == "" || !ValidType(typ) {
		err = fmt.Errorf("input type is invalid")
		return
	}

	tag = &Tag{}
	tag.Area = area
	tag.Code = code
	tag.Addr = addr
	tag.Typ = typ
	re := regexp.MustCompile(`^(\d+)(?:\.([0-9]|1[0-5]))?$`)
	match := re.FindStringSubmatch(addr)
	if len(match) == 3 {
		tag.Start, _ = strconv.Atoi(match[1])
		if match[2] != "" {
			tag.MBit, _ = strconv.Atoi(match[2])
		}
	} else {
		err = fmt.Errorf("input code is absent, code should be S7 syntax")
		return
	}
	switch tag.Typ {
	case "BOOL", "I16", "U16":
		tag.Quantity = 1 //1个 uint16
		break
	default:
		tag.Quantity = 2 //2个 uint16
		break
	}
	return
}

// ReadTag 读取节点数据
func ReadTag(buffer []byte, offset int, tag *Tag, endian string) (value any, err error) {
	mBit := 0
	if tag.Area == "C" || tag.Area == "D" {
		mBit = offset % 8
		offset = offset / 8
	}
	buf := buffer[offset:]
	switch tag.Typ {
	case "BOOL":
		if tag.Area == "C" || tag.Area == "D" {
			//log.Println("buf", buf, mBit)
			value = buf[0]&(1<<mBit) != 0
			return
		}
		if tag.MBit > 15 || tag.MBit < 0 {
			err = fmt.Errorf("read bit is invalid")
			return
		}
		uValue := binary.BigEndian.Uint16(buf[0:])
		mask := uint16(1 << tag.MBit)
		value = (uValue & mask) != 0
		return
	case "I16":
		if endian == "DCBA" || endian == "BADC" {
			value = int16(uint16(buf[0]) | uint16(buf[1])<<8)
		} else {
			value = int16(uint16(buf[1]) | uint16(buf[0])<<8)
		}
		return
	case "U16":
		if endian == "DCBA" || endian == "BADC" {
			value = uint16(buf[0]) | uint16(buf[1])<<8
		} else {
			value = uint16(buf[1]) | uint16(buf[0])<<8
		}
		return
	case "I32":
		if endian == "DCBA" {
			value = int32(uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24)
		} else if endian == "BADC" {
			value = int32(uint32(buf[2]) | uint32(buf[3])<<8 | uint32(buf[0])<<16 | uint32(buf[1])<<24)
		} else if endian == "CDAB" {
			value = int32(uint32(buf[1]) | uint32(buf[0])<<8 | uint32(buf[3])<<16 | uint32(buf[2])<<24)
		} else {
			value = int32(uint32(buf[3]) | uint32(buf[2])<<8 | uint32(buf[1])<<16 | uint32(buf[0])<<24)
		}
		return
	case "U32":
		if endian == "DCBA" {
			value = uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
		} else if endian == "BADC" {
			value = uint32(buf[2]) | uint32(buf[3])<<8 | uint32(buf[0])<<16 | uint32(buf[1])<<24
		} else if endian == "CDAB" {
			value = uint32(buf[1]) | uint32(buf[0])<<8 | uint32(buf[3])<<16 | uint32(buf[2])<<24
		} else {
			value = uint32(buf[3]) | uint32(buf[2])<<8 | uint32(buf[1])<<16 | uint32(buf[0])<<24
		}
		return
	case "FLOAT":
		temp := uint32(0)
		if endian == "DCBA" {
			temp = uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
		} else if endian == "BADC" {
			temp = uint32(buf[2]) | uint32(buf[3])<<8 | uint32(buf[0])<<16 | uint32(buf[1])<<24
		} else if endian == "CDAB" {
			temp = uint32(buf[1]) | uint32(buf[0])<<8 | uint32(buf[3])<<16 | uint32(buf[2])<<24
		} else {
			temp = uint32(buf[3]) | uint32(buf[2])<<8 | uint32(buf[1])<<16 | uint32(buf[0])<<24
		}
		value = math.Float32frombits(temp)
		return
	}
	return nil, errors.New(tag.Code + "数据类型无效")
}
